/* --------------------------------------------------------
 * Copyright (c) Express Scripts, Inc.  All rights reserved.
 * --------------------------------------------------------
 */
package com.aeky.server.dao.impl;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.Query;

import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Repository;

import com.aeky.common.FieldType;
import com.aeky.common.Page;
import com.aeky.server.dao.BaseDao;

/**
 * The DaoSupport Class which support Paginate Search.
 * 
 * @param <T> Domain object Class
 */
@SuppressWarnings("unchecked")
@Repository("baseDao")
public class BaseDaoSupport<T> extends SimpleDaoSupport<T> implements BaseDao<T> {

    /** The Constant FILENAME_DELIMITER. */
    private static final String FILENAME_DELIMITER = ".";

    /** The Constant FILENAME_DELIMITER_TRANSFERRED. */
    private static final String FILENAME_DELIMITER_TRANSFERRED = "\\.";

    /**
     * List by page.
     * 
     * @param map the map
     * @param page the page
     * 
     * @return the page< t>
     */
    public Page listByPage(Map<String, Object> map, Page<T> page) {
        Map<String, Object> params = new HashMap<String, Object>();
        StringBuffer queryString = getQueryString(map, params);
        return listByPage(queryString.toString(), params, page);
    }

    /**
     * List by page.
     * 
     * @param queryStr the query str
     * @param params the params
     * @param page the page
     * 
     * @return the page< t>
     */
    protected Page<T> listByPage(String queryStr, Map<String, Object> params, Page page) {
        queryStr += page.getOrderString();
        Query q = createQuery(queryStr, params, false);
        if (page.getLimit() != Integer.MAX_VALUE) {
            Integer totalCount = countHqlResult(queryStr, params, page).intValue();
            page.setTotalCount(totalCount);
            if (totalCount == 0) {
                return page;
            }
        }
        setPageParameter(q, page);
        List result = q.getResultList();
        page.setResult(result);
        if (page.getLimit() == Integer.MAX_VALUE) {
            page.setTotalCount(result.size());
        }
        return page;
    }

    /**
     * List by page.
     * 
     * @param queryStr the query str
     * @param page the page
     * @param params the params
     * 
     * @return the page< t>
     */
    protected Page<T> listByPage(String queryStr, Page<T> page, Object... params) {
        queryStr += page.getOrderString();
        Query q = createQuery(queryStr, params);
        if (page.getLimit() != Integer.MAX_VALUE) {
            Integer totalCount = countHqlResultWithVariable(queryStr, params).intValue();
            page.setTotalCount(totalCount.intValue());
            if (totalCount == 0) {
                return page;
            }
        }
        setPageParameter(q, page);
        List result = q.getResultList();
        page.setResult(result);
        if (page.getLimit() == Integer.MAX_VALUE) {
            page.setTotalCount(result.size());
        }
        return page;
    }

    /**
     * List by page native.
     * 
     * @param queryStr the query str
     * @param entityClass the entity class
     * @param params the params
     * @param page the page
     * @return the page
     */
    public Page listByPageNative(String queryStr, String entityClass, Map<String, Object> params, Page page) {
        return listByPageNative(queryStr, entityClass, params, page, null);
    }

    /**
     * List by page native.
     * 
     * @param queryStr the query str
     * @param entityClass the entity class
     * @param params the params
     * @param page the page
     * @return the page
     */
    public Page listByPageNative(String queryStr, String entityClass, Map<String, Object> params, Page page,
            String strDistinct) {
        Query q = createQuery(queryStr, params, true);
        Integer totalCount = countWhereHqlResultNative(queryStr, params, entityClass, page.getLimitTotalCount(),
                strDistinct).intValue();
        page.setTotalCount(totalCount.intValue());
        if (totalCount == 0) {
            return page;
        }
        setPageParameter(q, page);
        List result = q.getResultList();
        page.setResult(result);
        return page;
    }

    public Page listByPageNative(String queryStr, Map<String, Object> params, Page page) {
        Query q = createQuery(queryStr, params, true);
        if (page.getLimit() != Integer.MAX_VALUE) {
            Integer totalCount = countSqlResult(queryStr, params, page).intValue();
            page.setTotalCount(totalCount);
            if (totalCount == 0) {
                return page;
            }
        }
        setPageParameter(q, page);
        List result = q.getResultList();
        page.setResult(result);
        if (page.getLimit() == Integer.MAX_VALUE) {
            page.setTotalCount(result.size());
        }
        return page;
    }

    /**
     * Count where hql result native.
     * 
     * @param sql the sql
     * @param values the values
     * @param className the class name
     * @param limitTotalCount the limit total count
     * @param strDistinct the str distinct
     * @return the long
     */
    private Long countWhereHqlResultNative(String sql, Map<String, Object> values, String className,
            int limitTotalCount, String strDistinct) {
        String fromSql = sql;
        fromSql = "from " + StringUtils.substringAfter(fromSql, "from");
        fromSql = StringUtils.substringBefore(fromSql, "order by");
        if (StringUtils.isNotEmpty(strDistinct)) {
            fromSql = strDistinct + " " + fromSql;
        } else if (limitTotalCount != -1) {
            fromSql = "SELECT 1 " + fromSql;
        }
        String countHql = "";
        if (limitTotalCount == -1) {
            countHql = "SELECT COUNT(1) " + fromSql;
        } else {
            countHql = "SELECT COUNT(1) FROM (" + fromSql + " LIMIT 0," + limitTotalCount + ") c";
        }
        Query queryTotal = this.getEntityManager().createNativeQuery(countHql);
        BigInteger totalCount = (BigInteger) queryTotal.getSingleResult();
        return totalCount.longValue();
    }

    /**
     * List by page.
     * 
     * @param queryStr the query str
     * @param className the class name
     * @param fields the fields
     * @param page the page
     * @return the page
     */
    protected Page<T> listByPage(String queryStr, String className, Map<String, Object> fields, Page<T> page) {
        StringBuilder sb = new StringBuilder();
        sb.append(queryStr);
        return listByPage(sb, className, fields, page, false);
    }

    /**
     * Gets the hql condition.
     * 
     * @param sb the sb
     * @param fields the fields
     * @param parmas the parmas
     * @return the hql condition
     */
    private StringBuilder getHqlCondition(StringBuilder sb, Map<String, Object> fields, Map<String, Object> parmas) {
        for (String fieldName : fields.keySet()) {
            Object fieldValue = fields.get(fieldName);
            if (fieldValue != null) {
                FieldType fieldType = FieldType.getFieldType(fieldValue);
                sb.append(fieldType.getConditionString(parmas, fieldName, fieldValue));
            }
        }
        return sb;
    }

    /**
     * List by page.
     * 
     * @param sb the sb
     * @param className the class name
     * @param fields the fields
     * @param page the page
     * @param hasWhere the has where
     * @return the page
     */
    protected Page<T> listByPage(StringBuilder sb, String className, Map<String, Object> fields, Page<T> page,
            Boolean hasWhere) {
        Map<String, Object> params = new HashMap<String, Object>();
        String queryStr = "";
        if (hasWhere) {
            queryStr = getHqlCondition(sb, fields, params).toString();
        } else {
            queryStr = getWhereString(sb, fields, params).toString();
        }
        queryStr += page.getGroupBy();
        queryStr += page.getOrderStringByEntityClass(className);
        // params.clear();
        Query q = createQuery(queryStr, params, false);
        Integer totalCount = countHqlResult(queryStr, params, page).intValue();
        page.setTotalCount(totalCount.intValue());
        if (totalCount == 0) {
            return page;
        }
        setPageParameter(q, page);
        List result = q.getResultList();
        page.setResult(result);
        return page;
    }

    /**
     * Sets the page parameter.
     * 
     * @param q the q
     * @param page the page
     * 
     * @return the query
     */
    protected Query setPageParameter(final Query q, final Page<T> page) {
        q.setFirstResult(page.getOffset());
        q.setMaxResults(page.getLimit());
        if (-1 != page.getLimitTotalCount() && (page.getOffset() + page.getLimit()) > page.getLimitTotalCount()) {
            int limt = page.getLimitTotalCount() % page.getLimit();
            q.setMaxResults(limt);
        } else {
            q.setMaxResults(page.getLimit());
        }
        return q;
    }

    /**
     * Gets the query string.
     * 
     * @param map the map
     * @param params the params
     * 
     * @return the query string
     */
    protected StringBuffer getQueryString(Map<String, Object> map, Map<String, Object> params) {
        StringBuffer sb = new StringBuffer();
        sb.append("from ");
        sb.append(getEntityClass().getSimpleName());
        sb.append(" where 1=1");
        for (String fieldName : map.keySet()) {
            Object fieldValue = map.get(fieldName);
            if (fieldValue != null) {
                FieldType fieldType = FieldType.getFieldType(fieldValue);
                sb.append(fieldType.getConditionString(params, fieldName, fieldValue));
            }
        }
        return sb;
    }

    /**
     * Gets the where string.
     * 
     * @param sb the sb
     * @param map the map
     * @param params the params
     * @return the where string
     */
    protected StringBuilder getWhereString(StringBuilder sb, Map<String, Object> map, Map<String, Object> params) {
        sb.append(" where 1=1");
        for (String fieldName : map.keySet()) {
            Object fieldValue = map.get(fieldName);
            if (fieldValue != null) {
                FieldType fieldType = FieldType.getFieldType(fieldValue);
                sb.append(fieldType.getConditionString(params, fieldName, fieldValue));
            }
        }
        return sb;
    }

    /**
     * Count hql result.
     * 
     * @param hql the hql
     * @param values the values
     * 
     * @return the long
     */
    protected Long countHqlResult(final String hql, final Map<String, Object> values, Page page) {
        String tempHql = hql.replaceAll("inner join fetch", "inner join");
        tempHql = tempHql.replaceAll("left join fetch", "left join");
        String fromHql = tempHql;
        fromHql = new StringBuilder().append("from ").append(StringUtils.substringAfter(fromHql, "from")).toString();
        fromHql = StringUtils.substringBefore(fromHql, "order by");
        fromHql = StringUtils.substringBefore(fromHql, "group by");
        StringBuilder countHql;
        if (tempHql.startsWith("select distinct")) {
            String fieldsHql = StringUtils.substringBefore(tempHql, "from");
            fieldsHql = StringUtils.substringAfter(fieldsHql, "select ");
            fieldsHql = StringUtils.substringBefore(fieldsHql, ",");
            if (page.getLimitTotalCount() == -1) {
                countHql = new StringBuilder("select count(").append(fieldsHql).append(") ").append(fromHql);
            } else {
                countHql = new StringBuilder("select count(").append(fieldsHql).append(") ").append(fromHql);
            }
        } else {
            countHql = new StringBuilder().append("select count(*) ").append(fromHql);
        }
        try {
            Long count = 0L;
            count = findUnique(countHql.toString(), values);
            return count;
        } catch (Exception e) {
            throw new RuntimeException("hql can't be auto count, hql is:" + countHql, e);
        }
    }

    /**
     * Count where hql result native.
     * 
     * @param sql the sql
     * @param values the values
     * @param className the class name
     * @param limitTotalCount the limit total count
     * @param strDistinct the str distinct
     * @return the long
     */
    private Long countSqlResult(String sql, Map<String, Object> values, Page page) {
        String fromHql = sql;
        fromHql = new StringBuilder().append("from ").append(StringUtils.substringAfter(fromHql, "from")).toString();
        fromHql = StringUtils.substringBefore(fromHql, "order by");
        fromHql = StringUtils.substringBefore(fromHql, "group by");
        String fieldsHql = StringUtils.substringBefore(sql, "from");
        fieldsHql = StringUtils.substringAfter(fieldsHql, "select ");
        fieldsHql = StringUtils.substringBefore(fieldsHql, ",");
        StringBuilder countSql = new StringBuilder();
        if (page.getLimitTotalCount() == -1) {
            countSql = new StringBuilder("select count(").append(fieldsHql).append(") ").append(fromHql);
        } else {
            // hql get the max count limit
            countSql = new StringBuilder(" select count(1) from ( ");
            countSql = countSql.append("select ");
            countSql = countSql.append(fieldsHql);
            countSql = countSql.append(" ");
            countSql = countSql.append(fromHql);
            countSql = countSql.append(" limit ");
            countSql = countSql.append(page.getLimitTotalCount());
            countSql = countSql.append(" ) countTemp ");
        }
        try {
            BigInteger count = findUnique(countSql.toString(), values, true);
            return count.longValue();
        } catch (Exception e) {
            throw new RuntimeException("sql can't be auto count, sql is:" + countSql, e);
        }
    }

    /**
     * Count hql result.
     * 
     * @param hql the hql
     * @param values the values
     * 
     * @return the long
     */
    protected Long countHqlResultWithVariable(final String hql, final Object... values) {
        String fromHql = hql;
        fromHql = "from " + StringUtils.substringAfter(fromHql, "from");
        fromHql = StringUtils.substringBefore(fromHql, "order by");
        String countHql = "select count(*) " + fromHql;
        try {
            Long count = findUnique(countHql, values);
            return count;
        } catch (Exception e) {
            throw new RuntimeException("hql can't be auto count, hql is:" + countHql, e);
        }
    }
}
